﻿using System;
using System.Collections.Generic;
using System.Text;
using net.veekee.common;
using System.Reflection;
using System.Text.RegularExpressions;
using net.veekee.logprocessor.plugin;
using net.veekee.logprocessor.plugin.rules;
using net.veekee.logprocessor.plugin.context;
using net.veekee.logprocessor.plugin.input;
using net.veekee.logprocessor.plugin.output;
using net.veekee.common.reflection;
using System.IO;
using System.Linq;
using Ionic.Zip;

namespace net.veekee.logprocessor
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                Console.TreatControlCAsInput = false;

                Options.GetInstance().Add(new Option() { Name = "config", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "INI file", DefaultValue = System.IO.Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + ".ini" });
                Options.GetInstance().Add(new Option() { Name = "config.custom", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Allow custom variables", PossibleValues = new String[] { "true", "false" }, DefaultValue = "false" } );
                Options.GetInstance().Add(new Option() { Name = "log", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Log file" });
                Options.GetInstance().Add(new Option() { Name = "description", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Configuration description", DefaultValue = "" });
                Options.GetInstance().Add(new Option() { Name = "pwd", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Working path", DefaultValue = Environment.CurrentDirectory });
                Options.GetInstance().Add(new Option() { Name = "input.format", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), DefaultValue = "log", Description = "Input format", PossibleValues = InputFactory.GetLoadedExtensions() });
                Options.GetInstance().Add(new Option() { Name = "input", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsMandatory), Description = "Input files", FileMustExist = false });
                Options.GetInstance().Add(new Option() { Name = "input.onfinish.move", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "If set, move processed files into the targetted directory", FileMustExist = false, DefaultValue = "" });
                Options.GetInstance().Add(new Option() { Name = "input.archives.scan", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Search inside zip files", PossibleValues = new String[] { "true", "false" }, DefaultValue = "false" });
                Options.GetInstance().Add(new Option() { Name = "input.recursive", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Search recursively for files", PossibleValues = new String[] { "true", "false" }, DefaultValue = "false" });
                Options.GetInstance().Add(new Option() { Name = "input.encoding", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Set the encoding to use to open input files", PossibleValues = new String[] { Encoding.UTF8.HeaderName, Encoding.Default.HeaderName, Encoding.Unicode.HeaderName }, DefaultValue = Encoding.UTF8.HeaderName });
                Options.GetInstance().Add(new Option() { Name = "maxlevel", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), DefaultValue = "99", Description = "Max level in intercepted logs" });
                Options.GetInstance().Add(new Option() { Name = "debug", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = true, Description = "Enabled DEBUG mode" });
                Options.GetInstance().Add(new Option() { Name = "output", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), Description = "Output files", FileMustExist = false });
                Options.GetInstance().Add(new Option() { Name = "output.format", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), DefaultValue = "csv", Description = "Output format", PossibleValues = OutputFactory.GetLoadedExtensions() });
                Options.GetInstance().Add(new Option() { Name = "output.append", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = true, Description = "Prevent existing file to be deleted in CSV mode" });
                Options.GetInstance().Add(new Option() { Name = "output.encoding", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Set the encoding to use to write output file", PossibleValues = new String[] { Encoding.UTF8.HeaderName, Encoding.Default.HeaderName, Encoding.Unicode.HeaderName }, DefaultValue = Encoding.UTF8.HeaderName });
                Options.GetInstance().Add(new Option() { Name = "buffer.output", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Manages the max out buffer used by application (in entries)", DefaultValue = "99999" });
                Options.GetInstance().Add(new Option() { Name = "buffer.output.beforeflush", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Manages the max out buffer used by application (in entries)", DefaultValue = "50000" });
                Options.GetInstance().Add(new Option() { Name = "trace", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = true, Description = "Enabled DEBUG mode for rule processors" });
                Options.GetInstance().Add(new Option() { Name = "output.fields", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Set fields in output file, ';' seperated and 'Name<InnerField>' formatted" });
                Options.GetInstance().Add(new Option() { Name = "rules.filter", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Set Rule Filters to select wished rules, ';' seperated" });
                Options.GetInstance().Add(new Option() { Name = "rules.params", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Set Rule internal params, ';' seperated and 'Name<InnerField>' formatted" });
                Options.GetInstance().Add(new Option() { Name = "rules.processdiscarded", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = false, Description = "Define wether rules should be apply on discarded items or not", PossibleValues = new String[] { "true", "false" }, DefaultValue = "false" });
                Options.GetInstance().Add(new Option() { Name = "buffer.keepalive", MandatoryValidation = new MandatoryOptionValidatorHandler(OptionValidatorHandler.IsNotMandatory), NoArgument = true, Description = "Is set, entries in buffer will be kept between each file processing" });

                Options.GetInstance().Load("Command line", args);
                Options.GetInstance().LoadConfigFiles(Options.GetInstance().Get("config"));
                Options.GetInstance().Set("log", FileContext.GetFileName(Options.GetInstance().Get("log", ""), null));

                Log.GetInstance().Reset();
                Log.GetInstance().Info("Starting log processor");
                Log.GetInstance().Info("Log file: {0}", FileContext.GetFileName(Options.GetInstance().Get("log", ""), null));
                                
                Log.GetInstance().Info("Version: {0}", Options.GetVersion(typeof(Program)));
                Log.GetInstance().Info("Common DLL Version: {0}", Options.GetVersion(typeof(Options)));
                Log.GetInstance().Info("Plugin DLL Version: {0}", Options.GetVersion(typeof(PluginManager)));
                PluginManager.GetInstance().Load();

                //Options.GetInstance().Load("Command line", args);
                //Options.GetInstance().LoadConfigFiles(Options.GetInstance().Get("config"));
                FS.CheckOptionalizedPath("pwd");
                if (Options.GetInstance().NoArgumentsPassed)
                {
                    Log.GetInstance().Error("No argument found");
                    Options.GetInstance().Usage();
                    System.Environment.Exit(101);
                }
                if (!Options.GetInstance().Check())
                {
                    Log.GetInstance().Error("Argument check failed");
                    Options.GetInstance().Usage();
                    System.Environment.Exit(102);
                }

                if (Options.GetInstance().Get("description").Length > 0)
                {
                    Log.GetInstance().Info("Process description: {0}", Options.GetInstance().Get("description"));
                }

                Program.PrepareCompiler();

                int processedFiles = 0;
                int filesToProcess = 0;
                String[] inputfiles = Options.GetInstance().Get("input").Split(';');
                bool processRecursive = Options.GetInstance().Get("input.recursive", "false").Equals("true");
                bool processArchives = Options.GetInstance().Get("input.archives.scan", "false").Equals("true");
                filesToProcess = inputfiles.Length;
                for (int i = 0; i < inputfiles.Length; i++)
                {
                    String[] filea = inputfiles[i].Split(new char[] { '|' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (filea.Length <= 1)
                    {
                        processedFiles++;
                        Process("", inputfiles[i], i == 0, i == (inputfiles.Length - 1), processedFiles, filesToProcess);
                        ProcessOnFinish(inputfiles[i], true);
                    }
                    else
                    {
                        if (!System.IO.Directory.Exists(filea[0]))
                        {
                            Log.GetInstance().Error("Source directory '{0}' doesn't exist... discarting", filea[0]);
                            continue;
                        }
                        List<String> allfiles = new List<String>();
                        List<String> allarchivefiles = new List<String>();
                        Program.GetFiles(allfiles, allarchivefiles, filea[0], filea[1], processRecursive, processArchives);

                        Log.GetInstance().Info("{0} file{1} to process", allfiles.Count, allfiles.Count > 1 ? "s" : "");
                        filesToProcess += allfiles.Count;
                        if (processArchives)
                        {
                            Log.GetInstance().Info("{0} archive{1} to process", allarchivefiles.Count, allarchivefiles.Count > 1 ? "s" : "");
                        }

                        Regex mask = new Regex(filea[1]);
                        for (int a = 0; a < allarchivefiles.Count; a++)
                        {
                            Log.GetInstance().Info("Processing archive {0}", allarchivefiles[a]);
                            if (System.IO.Directory.Exists("temp"))
                            {
                                System.IO.Directory.Delete("temp", true);
                            }
                            System.IO.Directory.CreateDirectory("temp");
                            using (ZipFile zip1 = ZipFile.Read(allarchivefiles[a]))
                            {
                                var selection = (from e in zip1.Entries
                                                 where mask.IsMatch(e.FileName)
                                                 select e);
                                foreach (var e in selection)
                                {
                                    Log.GetInstance().Info("Preparing sub file {0} from archive {1}", e.FileName, allarchivefiles[a]);
                                    e.Extract("temp", ExtractExistingFileAction.OverwriteSilently);
                                    String simpleFileName = System.IO.Path.GetFileName(e.FileName);
                                    String simpleTreePath = System.IO.Path.GetDirectoryName(e.FileName);
                                    if (!simpleFileName.Equals(e.FileName))
                                    {
                                        System.IO.File.Copy("temp" + System.IO.Path.DirectorySeparatorChar + e.FileName, "temp" + System.IO.Path.DirectorySeparatorChar + simpleFileName);
                                        System.IO.Directory.Delete("temp" + System.IO.Path.DirectorySeparatorChar + simpleTreePath, true);
                                    }
                                }
                            }

                            List<String> archivesfiles = new List<String>(System.IO.Directory.GetFiles("temp", "*.*"));
                            filesToProcess += archivesfiles.Count;
                            Log.GetInstance().Info("{0} file{1} to process", archivesfiles.Count, archivesfiles.Count > 1 ? "s" : "");

                            for (int f = 0; f < archivesfiles.Count; f++)
                            {
                                processedFiles++;
                                Process(allarchivefiles[a], archivesfiles[f], (processedFiles == 1), (i == (inputfiles.Length - 1) && processedFiles == filesToProcess), processedFiles, filesToProcess);
                            }
                            System.IO.Directory.Delete("temp", true);
                            ProcessOnFinish(allarchivefiles[a], true);
                        }

                        for (int f = 0; f < allfiles.Count; f++)
                        {
                            processedFiles++;
                            Process("", allfiles[f], (processedFiles == 1), (i == (inputfiles.Length - 1) && processedFiles == filesToProcess), processedFiles, filesToProcess);
                            ProcessOnFinish(allfiles[f], true);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Internal crash: {0}", e.ToString());
            }
            Log.GetInstance().Info("eop");
        }

        private static void ProcessOnFinish(String file, bool result)
        {
            try
            {
                if (Options.GetInstance().Get("input.onfinish.move").Length > 0)
                {
                    String destination = Options.GetInstance().Get("input.onfinish.move");
                    FileInfo source = new FileInfo(file);
                    if (!Directory.Exists(destination))
                    {
                        Directory.CreateDirectory(destination);
                    }
                    if (source.Exists)
                    {
                        Log.GetInstance().Error("Moving file '{0}' to '{1}'", file, destination);
                        File.Move(file, Options.GetInstance().Get("input.onfinish.move") + Path.DirectorySeparatorChar + source.Name);
                    }
                    else
                    {
                        Log.GetInstance().Error("Couldn't process {0}: File '{1}' doesn't exist", "onfinish.move", file);
                    }
                }
            }
            catch(Exception e)
            {
                Log.GetInstance().Error("Couldn't process {0}: {1}", "onfinish", e);
            }
        }
        
        private static void PrepareCompiler()
        {
            DynamicClass.DefaultBusinessImports.Add("net.veekee.common");
            DynamicClass.DefaultBusinessImports.Add("net.veekee.common.data");
            DynamicClass.DefaultBusinessImports.Add("net.veekee.logprocessor.plugin.input");
            DynamicClass.DefaultBusinessImports.Add("net.veekee.logprocessor.plugin.rules");
        }

        private static void GetFiles(List<String> list, List<String> archivelist, String folder, String filePattern, bool recursive, bool scanArchives)
        {
            try
            {
                foreach (String f in System.IO.Directory.GetFiles(folder, filePattern))
                {
                    list.Add(f);
                }
                if (scanArchives)
                {
                    foreach (String f in System.IO.Directory.GetFiles(folder, "*.zip"))
                    {
                        archivelist.Add(f);
                    }
                }
                if (recursive)
                {
                    foreach (String d in System.IO.Directory.GetDirectories(folder))
                    {
                        Program.GetFiles(list, archivelist, d, filePattern, recursive, scanArchives);
                    }
                }
            }
            catch (System.Exception e)
            {
                Log.GetInstance().Error("File search failed: {0}", e.ToString());
            }
        }

        private static void Process(String inputarchive, String inputfile, bool isFirstFile, bool isLastFile, int fileID, int fileCount)
        {
            RuleEnvironment.GetInstance().Reload(Options.GetInstance().Get("rules.params", ""));

            String originalFilename = FS.ManageWorkingDir(inputfile);

            ProcessorContext.GetInstance().SetInput(inputfile);
            ProcessorContext.GetInstance().AddOutput(ProcessorContext.DefaultOutputFilename, Options.GetInstance().Get("output"));
            ProcessorContext.GetInstance().RenameOutputs();
            
            Console.CancelKeyPress += new ConsoleCancelEventHandler(delegate(object sender, ConsoleCancelEventArgs e)
                                                                    {
                                                                        e.Cancel = true;
                                                                        if (e.Cancel)
                                                                        {
                                                                            Console.WriteLine("Exiting...");
                                                                            System.Environment.Exit(1);
                                                                        }
                                                                    });

            AParser.SetInstance(InputFactory.Create(Options.GetInstance().Get("input.format")));
            if (AParser.GetInstance() == null)
            {
                Log.GetInstance().Error("No parser found... exiting");
                System.Environment.Exit(105);
            }
            DateTime reference = DateTime.Now;
            if (!AParser.GetInstance().Parse(inputarchive, originalFilename, isFirstFile, isLastFile, fileID, fileCount))
            {
                Log.GetInstance().Error("Parsing error... exiting");
                System.Environment.Exit(106);
            }
            TimeSpan processTime = DateTime.Now.Subtract(reference);
            Log.GetInstance().Info("File processed in {0}", processTime.ToString());
        }
    }
}
